﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.IO;
using System.Net;
using System.Web;
using System.Collections.Specialized;

namespace Devfw.OpenAuth
{
    /// <summary>
    /// OpenAuth helper
    /// </summary>
    public static class OpenAuthHelper
    {
        #region Parameter Encode
        const string UNRESERVED_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
        /// <summary>
        /// see Parameter Encoding at http://oauth.net/core/1.0/#rfc.section.5.1
        /// It difference than HttpUtility.UrlEncode, HttpUtility.UrlEncode result is lower case, Parameter encode resut is upper case(OAuth Rule)
        /// </summary>
        /// <param name="value">The value to parameter encode</param>
        /// <returns>Returns a parameter encoded string</returns>
        public static string ParameterEncode1( string value )
        {
            if( string.IsNullOrEmpty( value ) )
            {
                return string.Empty;
            }

            StringBuilder result = new StringBuilder();

            foreach( char symbol in value )
            {
                if( UNRESERVED_CHARS.IndexOf( symbol ) != -1 )
                {
                    result.Append( symbol );
                }
                else
                {
                    result.Append( '%' + String.Format( "{0:X2}", (int)symbol ) );
                }
            }

            return result.ToString();
        }

        /// <summary>
        /// This is a different Url Encode implementation since the default .NET one outputs the percent encoding in lower case.
        /// While this is not a problem with the percent encoding spec, it is used in upper case throughout OAuth
        /// </summary>
        /// <param name="value">The value to Url encode</param>
        /// <returns>Returns a Url encoded string</returns>
        public static string ParameterEncode( string value )
        {
            if (string.IsNullOrEmpty(value))
                return "";

            StringBuilder result = new StringBuilder();
            byte[] byStr = System.Text.Encoding.UTF8.GetBytes( value );

            foreach( byte symbol in byStr )
            {
                if( UNRESERVED_CHARS.IndexOf( (char)symbol ) != -1 )
                {
                    result.Append( (char)symbol );
                }
                else
                {
                    result.Append( '%' + Convert.ToString( (char)symbol, 16 ).ToUpper() );
                }
            }

            return result.ToString();
        }

        #endregion

        #region Url Operate
        public static string CombinUrl( string url, string query )
        {
            if( string.IsNullOrEmpty( query ) )
            {
                return url;
            }
            if( url.IndexOf( "?" ) > 0 )
            {
                url += "&";
            }
            else
            {
                url += "?";
            }
            return url + query;
        }

        public static string CombinUrl( string url, List<Parameter> list )
        {
            StringBuilder sb = new StringBuilder();

            foreach( var p in list )
            {
                sb.AppendFormat( string.Format( "{0}={1}&", p.Name, ParameterEncode( p.Value ) ) );
            }
            if( sb.Length > 0 )
            {
                sb.Remove( sb.Length - 1, 1 );
            }
            return CombinUrl( url, sb.ToString() );
        }

        public static string CombinQueryString( string part1, string part2 )
        {
            if( string.IsNullOrEmpty( part1 ) )
            {
                return part2;
            }
            if( string.IsNullOrEmpty( part2 ) )
            {
                return part1;
            }

            return string.Format( "{0}&{1}", part1, part2 );
        }

        public static string ToQueryString( params Parameter[] list )
        {
            StringBuilder sb = new StringBuilder();
            foreach( var p in list )
            {
                sb.AppendFormat( "{0}={1}&", p.Name,OpenAuthHelper.ParameterEncode( p.Value ));
            }
            if( sb.Length > 0 )
            {
                sb.Remove( sb.Length - 1, 1 );
            }
            return sb.ToString();
        }

        public static string ToPostBody( params Parameter[] list )
        {
            StringBuilder sb = new StringBuilder();
            foreach( var p in list )
            {
                if( string.IsNullOrEmpty( p.Name ) )
                {
                    sb.AppendFormat( "{1}&", p.Name, p.NeedEncodePostData ? OpenAuthHelper.ParameterEncode( p.Value ) : p.Value );
                }
                else
                {
                    sb.AppendFormat("{0}={1}&", p.Name, p.NeedEncodePostData ? OpenAuthHelper.ParameterEncode(p.Value) : p.Value);
                }
            }
            if( sb.Length > 0 )
            {
                sb.Remove( sb.Length - 1, 1 );
            }
            return sb.ToString();
        }


        public static string ToPutBody(params Parameter[] list)
        {
            StringBuilder sb = new StringBuilder();
            foreach (var p in list)
            {
               sb.AppendFormat(p.Value);
            }
           
            return sb.ToString();
        }
        #endregion

        #region Normalize
        /// <summary>
        /// see Construct Request URL at http://oauth.net/core/1.0/#rfc.section.9.1.2
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public static string ConstructRequestUrl( string uriString )
        {
            return ConstructRequestUrl( new Uri( uriString ) );
        }

        /// <summary>
        /// see Construct Request URL at http://oauth.net/core/1.0/#rfc.section.9.1.2
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public static string ConstructRequestUrl( Uri url )
        {
            string normalizedUrl = string.Format( "{0}://{1}", url.Scheme, url.Host );
            if( !( ( url.Scheme == "http" && url.Port == 80 ) || ( url.Scheme == "https" && url.Port == 443 ) ) )
            {
                normalizedUrl += ":" + url.Port;
            }
            normalizedUrl += url.AbsolutePath;
            return normalizedUrl;
        }

        /// <summary>
        /// see Normalizes request parameters at http://oauth.net/core/1.0/#rfc.section.9.1.1
        /// Normalizes the request parameters according to the spec
        /// </summary>
        /// <param name="parameters">The list of parameters (no need sorted)</param>
        /// <returns>a string representing the normalized parameters</returns>
        public static string NormalizeRequestParameters( List<Parameter> parameters )
        {
            parameters.Sort( new ParameterComparer() );

            StringBuilder sb = new StringBuilder();
            // Parameter p = null;
            // for( int i = 0; i < parameters.Count; i++ )
            foreach( var p  in parameters )
            {
                if( string.IsNullOrEmpty( p.Name ) )
                {
                    sb.AppendFormat("={1}&", p.Name, ParameterEncode(p.Value));
                }
                else
                {
                    sb.AppendFormat( "{0}={1}&", p.Name, ParameterEncode( p.Value ) );
                }
            }

            // remove last '&' char
            if( sb.Length > 0 )
            {
                sb.Remove( sb.Length - 1, 1 );
            }

            return sb.ToString();
        }
        #endregion

        #region Open Auth Data Generate
        /// <summary>
        /// Generate the timestamp for the signature        
        /// </summary>
        /// <returns></returns>
        public static string GenerateTimeStamp()
        {
            // Default implementation of UNIX time of the current UTC time
            TimeSpan ts = DateTime.UtcNow - new DateTime( 1970, 1, 1, 0, 0, 0, 0 );
            return Convert.ToInt64( ts.TotalSeconds ).ToString();
        }

        private static Random random = new Random();

        /// <summary>
        /// Generate a nonce
        /// </summary>
        /// <returns></returns>
        public static string GenerateNonce()
        {
            // Just a simple implementation of a random number between 123400 and 9999999
            return random.Next( 123400, 9999999 ).ToString();
        }
        #endregion

        #region Signature
        public static ParameterCollection CombinSignatureParameters( string oauth_consumer_key, string oauth_token, string oauth_signature_method, string oauth_timestamp,
            string oauth_nonce, string oauth_version, ParameterCollection customParameter )
        {
            customParameter = customParameter ?? new ParameterCollection();

            ParameterCollection parameters = new ParameterCollection( customParameter );
            // List<Parameter> parameters = Parameter.Parse( nvcQueryString );

            parameters.Add( new Parameter( "oauth_consumer_key", oauth_consumer_key, ParameterType.OAuth ) );
            parameters.Add( new Parameter( "oauth_signature_method", oauth_signature_method, ParameterType.OAuth ) );
            parameters.Add( new Parameter( "oauth_timestamp", oauth_timestamp, ParameterType.OAuth ) );
            parameters.Add( new Parameter( "oauth_nonce", oauth_nonce, ParameterType.OAuth ) );
            parameters.Add( new Parameter( "oauth_version", oauth_version, ParameterType.OAuth ) );

            if( !string.IsNullOrEmpty( oauth_token ) )
            {
                parameters.Add( new Parameter( "oauth_token", oauth_token, ParameterType.OAuth ) );
            }

            //parameters.Sort();
            return parameters;
        }

        public static string GenerateSignatuerBaseString( string httpMethod, string absoluteUri, List<Parameter> parameters )
        {
            // 新增排除不参与签名的参数
            var signatureList = parameters.Where(p => p.NeedSignature).ToList();
            signatureList.Sort(new ParameterComparer());

            return GenerateSignatuerBaseString(httpMethod, absoluteUri, OpenAuthHelper.NormalizeRequestParameters(signatureList));
        }

        /// <summary>
        /// see Generating Signature Base String at http://oauth.net/core/1.0/#rfc.section.A.5.1
        /// </summary>
        /// <param name="httpMethod"></param>
        /// <param name="absoluteUri"></param>
        /// <param name="normalizedRequestParametersString"></param>
        /// <returns></returns>
        public static string GenerateSignatuerBaseString( string httpMethod, string absoluteUri, string normalizedRequestParametersString )
        {
            StringBuilder signatureBase = new StringBuilder();
            signatureBase.AppendFormat( "{0}&", httpMethod.ToUpper() );
            signatureBase.AppendFormat( "{0}&", OpenAuthHelper.ParameterEncode( absoluteUri ) );
            signatureBase.AppendFormat( "{0}", OpenAuthHelper.ParameterEncode( normalizedRequestParametersString ) );

            return signatureBase.ToString();
        }

        public static ParameterCollection Signature( string httpMethod, string absoluteUri, ParameterCollection listParameter, string oauth_consumer_key,
            string oauth_consumer_secret, string oauth_token, string oauth_token_secret, string oauth_timestamp,
            string oauth_nonce, string oauth_version, SignatureType sigType )
        {
            // 参数集合，包含签名结果参数
            ParameterCollection listParametersReturn = null;

            // 生成待加密的参数列表
            listParametersReturn = OpenAuthHelper.CombinSignatureParameters( oauth_consumer_key, oauth_token,
                SignatureType.HMACSHA1.AdapterToString(), oauth_timestamp, oauth_nonce, oauth_version, listParameter );

            // 将参数列表转换成基准加密字符串
            string signatureBaseString = OpenAuthHelper.GenerateSignatuerBaseString( httpMethod, absoluteUri, listParametersReturn );
            // listParametersReturn.Add( "signatureBaseString", signatureBaseString, ParameterType.Unknown );

            string oauth_signature = null;
            switch( sigType )
            {
                case SignatureType.PLAINTEXT:
                    oauth_signature = OpenAuthHelper.CalculateSignatureValueByPLAINTEXT( oauth_consumer_secret, oauth_token_secret );
                    break;
                case SignatureType.HMACSHA1:
                    oauth_signature = OpenAuthHelper.CalculateSignatureValueByHMACSHA1( signatureBaseString, oauth_consumer_secret, oauth_token_secret );
                    break;
                default:
                    throw new ArgumentException( "Unknown signature type", "signatureType" );
            }
            listParametersReturn.Add( "oauth_signature", oauth_signature, ParameterType.OAuth );

            return listParametersReturn;
        }
        

        /// <summary>
        /// see Calculating Signature Value at http://oauth.net/core/1.0/#rfc.section.A.5.2
        /// </summary>
        /// <param name="signatureBaseString"></param>
        /// <param name="oauth_consumer_secret"></param>
        /// <param name="oauth_token_secret"></param>
        /// <returns></returns>
        public static string CalculateSignatureValueByHMACSHA1( string signatureBaseString, string oauth_consumer_secret, string oauth_token_secret )
        {
            HMACSHA1 hmacsha1 = new HMACSHA1();
            hmacsha1.Key = Encoding.ASCII.GetBytes( string.Format( "{0}&{1}", OpenAuthHelper.ParameterEncode( oauth_consumer_secret ), OpenAuthHelper.ParameterEncode(  oauth_token_secret ) ) );

            return ComputeHash( hmacsha1, signatureBaseString );
        }

        /// <summary>
        /// see PLAINTEXT at http://oauth.net/core/1.0/#rfc.section.9.4
        /// </summary>
        /// <param name="oauth_consumer_secret"></param>
        /// <param name="oauth_token_secret"></param>
        /// <returns></returns>
        public static string CalculateSignatureValueByPLAINTEXT( string oauth_consumer_secret, string oauth_token_secret )
        {
            return ParameterEncode( string.Format( "{0}&{1}", oauth_consumer_secret, oauth_token_secret ) );
        }

        /// <summary>
        /// Helper function to compute a hash value
        /// </summary>
        /// <param name="hashAlgorithm">The hashing algoirhtm used. If that algorithm needs some initialization, like HMAC and its derivatives, they should be initialized prior to passing it to this function</param>
        /// <param name="data">The data to hash</param>
        /// <returns>a Base64 string of the hash value</returns>
        public static string ComputeHash( HashAlgorithm hashAlgorithm, string data )
        {
            if( hashAlgorithm == null )
            {
                throw new ArgumentNullException( "hashAlgorithm" );
            }

            if( string.IsNullOrEmpty( data ) )
            {
                throw new ArgumentNullException( "data" );
            }

            byte[] dataBuffer = System.Text.Encoding.ASCII.GetBytes( data );
            byte[] hashBytes = hashAlgorithm.ComputeHash( dataBuffer );

            return Convert.ToBase64String( hashBytes );
        }
        #endregion

        #region Web Request
        public static string WebRequest(HttpWebMethod method, string url, string requestData, string httpAuthHeader)
        {
            return WebRequest(method, url,"application/x-www-form-urlencoded", requestData, httpAuthHeader);
        }

        public static string WebRequest(HttpWebMethod method, string url, string contentType, string requestData, string httpAuthHeader)
        {
            return WebRequest(method, url, contentType, Encoding.GetEncoding("iso-8859-1"), requestData, httpAuthHeader);
        }

        public static string WebRequest( HttpWebMethod method, string url, string contentType, Encoding  encoding, string requestData, string httpAuthHeader )
        {
            HttpWebRequest webRequest = null;
            StreamWriter requestWriter = null;
            string responseData = "";

            webRequest = System.Net.WebRequest.Create( url ) as HttpWebRequest;
            webRequest.Method = method.ToString();
            webRequest.Timeout = 10000;
            webRequest.ServicePoint.Expect100Continue = false;

            if( !string.IsNullOrEmpty( httpAuthHeader ) )
            {
                webRequest.Headers.Add( "Authorization", httpAuthHeader );
            }


            if( method == HttpWebMethod.POST || method == HttpWebMethod.PUT || method == HttpWebMethod.DELETE)
            {
                webRequest.ContentType = contentType;
                webRequest.Method = method.ToString();
                requestWriter = new StreamWriter(webRequest.GetRequestStream(), encoding);
                try
                {
                    requestWriter.Write(requestData);
                }
                catch
                {
                    throw;
                }
                finally
                {
                    if( requestWriter != null )
                    {
                        requestWriter.Close();
                        requestWriter = null;
                    }
                }
            }

            responseData = OpenAuthHelper.WebRequest( webRequest );
            webRequest = null;

            return responseData;
        }

        /// <summary>
        /// Try do web request
        /// </summary>
        /// <param name="webRequest"></param>
        /// <returns></returns>
        public static string WebRequest( HttpWebRequest webRequest )
        {
            string responseData = "";

            try
            {
                using( var webResponse = webRequest.GetResponse() )
                {
                    using( StreamReader sr = new StreamReader( webResponse.GetResponseStream() ) )
                    {
                        responseData = sr.ReadToEnd();
                    }
                }
                return responseData;
            }   
            catch( HttpException )
            {
                throw;
            }
            catch( WebException ex )
            {
                
                using( StreamReader sr = new StreamReader( ex.Response.GetResponseStream() ) )
                {
                    HttpWebResponse resp = ex.Response as HttpWebResponse;
                    if( resp == null )
                    {
                        throw;
                    }
                    else
                    {
                        throw new HttpException( (int)resp.StatusCode, /*ex.Message + "：" + */sr.ReadToEnd(), ex );
                    }
                }                
            }
            catch( Exception )
            {
                throw;
            }
        }
        #endregion
    }
}